package hermesV3.services.Space;

import hermesV3.Message;
import hermesV3.agent.Identifier;
import hermesV3.agent.InteractionContainer;
import hermesV3.agent.UserAgent;
import hermesV3.protocols.PhantomProtocol;
import hermesV3.protocols.SplitSpaceProtocol;
import hermesV3.protocols.SynProtocol;
import hermesV3.services.HubAgent;
import hermesV3.util.HermesLogger;
import it.unicam.cs.cosy.bioshape.core.components.Entity;
import it.unicam.cs.cosy.bioshape.core.components.Pair;
import it.unicam.cs.cosy.bioshape.core.components.ProtoEntity;
import it.unicam.cs.cosy.bioshape.core.components.WallEntity;
import it.unicam.cs.cosy.bioshape.core.components.exception.NotEnoughSpaceException;
import it.unicam.cs.cosy.bioshape.core.shapes.BoundingBox;
import it.unicam.cs.cosy.bioshape.core.shapes.Bounds;
import it.unicam.cs.cosy.bioshape.core.util.Messages;
import it.unicam.cs.cosy.bioshape.core.util.XmlReader;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;

/**
 * Manages spatial features such as space portions and entities in such spaces.
 * In particular, assigns to each {@code LocalCoordinator} the necessary
 * elements for the simulation: the bounds, the entities and the confining
 * walls.<br>
 * During simulation steps it manages the PhantomEntites exchange between the
 * {@code LocalCoordinator}s. Generally speaking, we have to check which
 * {@code LocalCoordinator} owns a passed entity.
 * 
 * @see it.unicam.cs.cosy.bioshape.core.components.LocalCoordinator
 * @see hermesV3.agent.Agent
 * 
 * @author Gaston Alanis, Matteo Micheletti, Andrea Piermarteri, Federico Buti
 */
public class SpaceManager extends UserAgent {

	private static final long serialVersionUID = -4178373021064778067L;
	private ArrayList<ProtoEntity> entityDataList = new ArrayList<ProtoEntity>();
	// Space limits
	private Point3d[] bounds = new Point3d[2];
	// Space volume
	private double spaceVolume;
	// Protocols
	private ArrayList<Identifier> listOfLocals = new ArrayList<Identifier>();
	private ExecutorService handlersPool;
	private ArrayList<Future<Map<Identifier, ArrayList<Entity>>>> results;
	private ArrayList<WallEntity> spaceWalls = new ArrayList<WallEntity>();
	private HashMap<Identifier, Point3d[]> localsBounds = 
	                                new HashMap<Identifier, Point3d[]>();
	private Pair<Identifier, Bounds>[] lcBounds;
	private Identifier globalId;
	private XmlReader conf;
	private int[] cards;
	private boolean respond;

	// value are set in the order in which they appear in vecmath
	/** split on Z axis */
	public static final int SPLITZ = 2;
	/** split on Y axis */
	public static final int SPLITY = 1;
	/** split on X axis */
	public static final int SPLITX = 0;
	private static int SPLITTYPE; // default split on X

	/**
	 * A {@link SpaceManager} with a name.
	 * 
	 * @param UserAgentName
	 *            name of the agent
	 * @param conf
	 *            The XML input used to set up simulation
	 */
	public SpaceManager(String UserAgentName, XmlReader conf) {
		super(UserAgentName);
		this.conf = conf;
	}

	/**
	 * Executes tasks related to pre-simulation. (1) Bounds calculation for each
	 * local prior to the simulation, (2) entities assignment to the locals, (3)
	 * walls assignment to the locals. If everything goes right during these
	 * phases the agent sends a
	 * {@code SplitSpaceProtocol#INTERACTIONDIVISIONDONE} interaction whereas if
	 * one of the points goes wrong (such as the entities distribution in the
	 * space) it sends a {@code SplitSpaceProtocol#INTERACTIONERROR} interaction
	 * to signal the problem and abort the simulation.
	 */
	@SuppressWarnings("unchecked")
	@Override
	public void init() {
		reception();
		HermesLogger.log(HermesLogger.INFO, this.getAgentName()
				+ " ON and waiting for new messages...");
		// 0. WAITS HUB for GlobalCoordinator ID...
		Message<?> msg = Messages.receiveSyn(this, HubAgent.class);
		if (msg == null) {
			HermesLogger.log(HermesLogger.ERROR, this.getAgentName()
					+ " something wrong with the communication "
					+ "with the HUB...Simulation stopped!");
			return;
		}
		InteractionContainer ic = (InteractionContainer) msg.getObject();
		globalId = (Identifier) ic.getParameter(SynProtocol.INTERACTIONSYN);
		// 1. gets LCs from GC and send an ack OR termination by SimulationAgent
		msg = getMessageSynch();
		if (!msg.getSenderAgentId().equals(globalId))
			return; // TERMINATE
		// else...
		ic = (InteractionContainer) msg.getObject();
		listOfLocals = (ArrayList<Identifier>) ic
				.getParameter(SplitSpaceProtocol.INTERACTIONSENDIDS);
		// prepares an adequate number of threads for answer questions.
		handlersPool = Executors.newFixedThreadPool(listOfLocals.size());
		results = new ArrayList<Future<Map<Identifier, ArrayList<Entity>>>>(
				listOfLocals.size());
		Messages.sendAck(this, globalId);
		// 2. calculate simulation volume, walls and generate all EntityDatas
		SPLITTYPE = conf.getCuttingAxis();
		bounds = conf.getRandomBounds();
		if (bounds == null)
			bounds = conf.getSimulationBounds();
		computeSpaceVolume();
		spaceWalls = generateWalls();
		entityDataList = conf.getProtos();
		System.out.println("entityDataList: ");
		for(ProtoEntity p : entityDataList)
		    System.out.println(p);
		// 3. Split space among LocalCoordinators
		localsBounds = splitSpace();
		// 4. Check user correctly if placement has been defined by the user or not
		if (checkPlacement())
			// TODO issue a termination for the error!!
			System.out.println("Space Manager: errore Placement!!");
		if (doPlacement()) {
			Placement positioner = new Placement(bounds, spaceVolume);
			try {
				positioner.place(entityDataList, false);
			} catch (NotEnoughSpaceException e) {
				HermesLogger.log(HermesLogger.ERROR, this.getAgentName()
						+ " placement task cannot be accomplished. Not enough"
						+ " space...");
				HermesLogger.log(HermesLogger.ERROR, this.getAgentName()
						+ " simulation is aborted...");
				Messages.sendInteraction(this, globalId,
						new SplitSpaceProtocol(),
						SplitSpaceProtocol.INTERACTIONERROR);
				return;
			}
			entityDataList = positioner.setEntityCentres();
		}
		// 5. split space among locals and set entities ownership
		HashMap<Identifier, ArrayList<ProtoEntity>> entitiesAssigned = 
		                    distributeEntities(entityDataList);
		// 6. Send data to locals
		if (!sendWalls()) {
			Messages.sendInteraction(this, globalId, new SplitSpaceProtocol(),
					SplitSpaceProtocol.INTERACTIONERROR);
			return;
		}
		if (!receiveLocalsAcks()) {
			Messages.sendInteraction(this, globalId, new SplitSpaceProtocol(),
					SplitSpaceProtocol.INTERACTIONERROR);
			return;
		}
		if (!sendLocalsData(entitiesAssigned)) {
			Messages.sendInteraction(this, globalId, new SplitSpaceProtocol(),
					SplitSpaceProtocol.INTERACTIONERROR);
			return;
		}
		// 7. last acks from locals for info reception...
		if (!receiveLocalsAcks()) {
			Messages.sendInteraction(this, globalId, new SplitSpaceProtocol(),
					SplitSpaceProtocol.INTERACTIONERROR);
			return;
		}
		HermesLogger.log(HermesLogger.INFO, this.getAgentName()
				+ " - locals setup completed, informing GlobalCoordinator...");
		// 8. communicate to the GC that it is ready
		Messages.sendInteraction(this, globalId, new SplitSpaceProtocol(),
				SplitSpaceProtocol.INTERACTIONDIVISIONDONE);
		HermesLogger.log(HermesLogger.INFO, this.getAgentName()
				+ " - simulation set-up accomplished, ready to start...");
		// 9. busy waiting for requests during simulation...
		respond = true;
		while (respond) {
			handleSpatialRequests();
		}
	}

	/**
	 * Checks that all the {@code ProtoEntity} are defined homogeneously w.r.t.
	 * the definition of their centres. In particular, it checks that <i>all</i>
	 * the {@code ProtoEntity} <i>has</i>(or <i>has not</i>) placement centres.
	 * 
	 * @return {@code true} if all the {@code ProtoEntity} has a centre;
	 *         {@code true} also if all the {@code ProtoEntity} has not a
	 *         centre; {@code false} in all the other cases.
	 */
	private boolean checkPlacement() {
		boolean check = entityDataList.get(0).getCenters()[0] != null;
		for (int i = 1; i < entityDataList.size(); i++)
			if (entityDataList.get(i).getCenters()[0] != null && !check)
				return false;
			else if (entityDataList.get(i).getCenters()[0] == null && check)
				return true;
		return true;
	}

	/**
	 * Checks if placement must be executed by the {@code SpaceManager}.
	 * 
	 * @return {@code true} if placement is need, {@code false} otherwise.
	 */
	private boolean doPlacement() {
		return entityDataList.get(0).getCenters()[0] == null;
	}

	/**
	 * Assigns to each LocalCoordinator the entities that are housed into the
	 * bounds previously assigned to them. Since the splitting of space is done
	 * w.r.t. the ORDER of the LC in the data structure, this aspect is
	 * exploited to easily calculate the {@code LocalCoordinator} to which an
	 * entity belongs.
	 * 
	 * The splitting offset is calculated for the splitting coordinate. Then,
	 * every entity considered has its splitting coordinates divided per the
	 * offset: the integer value returned corresponds to the
	 * {@code Identifier} index in the Locals data structure. The entity
	 * belongs exactly to the found Local.
	 * 
	 * @param toAssign
	 *            The {@code ArrayList} of {@code ProtoEntity} which should be
	 *            assigned to the {@code LocalCoordinator}s.
	 * 
	 * @return an HashTable where the keys are the LocalCoordinator IDs and the
	 *         values are all the ShapedEntites that will be housed into the
	 *         matching LocalCoordinator space.
	 * 
	 * @see it.unicam.cs.cosy.bioshape.core.components.LocalCoordinator
	 */
	private HashMap<Identifier, ArrayList<ProtoEntity>> distributeEntities(
			ArrayList<ProtoEntity> toAssign) {
		HashMap<Identifier, ArrayList<ProtoEntity>> entitiesToSend = 
		                    new HashMap<Identifier, ArrayList<ProtoEntity>>();
		cards = new int[listOfLocals.size()];
		// initialize structure
		for (Identifier i : this.listOfLocals)
			entitiesToSend.put(i, new ArrayList<ProtoEntity>());
		// check cases...
		double offset = Double.NEGATIVE_INFINITY;
		switch (SPLITTYPE) {
		case SPLITX:
			// calculate X offset
			offset = (bounds[1].x - bounds[0].x) / listOfLocals.size();
			// the int part corresponds to the index of the LC owning it
			for (ProtoEntity eD : toAssign) {
				for (Vector3d center : eD.getCenters()) {
					int index = (int) ((center.x - bounds[0].x) / offset);
					cards[index]++;
					Identifier id = listOfLocals.get(index);
					if (!entitiesToSend.get(id).contains(eD))
						entitiesToSend.get(id).add(eD);
				}
			}
			break;
		case SPLITY:
			// calculate Y offset
			offset = (bounds[1].y - bounds[0].y) / listOfLocals.size();
			// the int part corresponds to the index of the LC owning it
			for (ProtoEntity eD : toAssign) {
				for (Vector3d center : eD.getCenters()) {
					int index = (int) ((center.y - bounds[0].y) / offset);
					cards[index]++;
					Identifier id = listOfLocals.get(index);
					if (!entitiesToSend.get(id).contains(eD))
						entitiesToSend.get(id).add(eD);
				}
			}
			break;
		case SPLITZ:
			// calculate Z offset
			offset = (bounds[1].z - bounds[0].z) / listOfLocals.size();
			// the int part corresponds to the index of the LC owning it
			for (ProtoEntity eD : toAssign) {
				for (Vector3d center : eD.getCenters()) {
					int index = (int) ((center.z - bounds[0].z) / offset);
					cards[index]++;
					Identifier id = listOfLocals.get(index);
					if (!entitiesToSend.get(id).contains(eD))
						entitiesToSend.get(id).add(eD);
				}
			}
		}
		return entitiesToSend;
	}

	/**
	 * Sends the {@link ProtoEntity}, the space limits, the scale and the
	 * starting index to each {@code LocalCoordinator}. In particular the index
	 * 0 is passed to the first local. To the following the initial index is
	 * exactly equals to the previous one plus the cardinality of the entities
	 * to create. For instance, if the local 0 has 10 instances, local 1 has 20
	 * instances and local 2 has 5 instances the passed indexes would be: 0 - 10
	 * - 30. The last local will use indexes 30-34 to uniquely identify its
	 * entities.
	 * 
	 * @see it.unicam.cs.cosy.bioshape.core.components.LocalCoordinator
	 * @see it.unicam.cs.cosy.bioshape.core.components.ShapedEntity
	 * 
	 * @param entitiesAssigned
	 *            the {@link ProtoEntity} to assign, that is an HashTable where
	 *            the the keys are the the LocalCoordinator IDs and the values
	 *            are the ShapedEntities to assign to them.
	 * 
	 */
	private boolean sendLocalsData(
			HashMap<Identifier, ArrayList<ProtoEntity>> entitiesAssigned) {
		int indexes = 6; // 0-5 reserved to walls!!
		for (Identifier local : listOfLocals) {
			Object[] params = new Object[1];
			InteractionContainer ic = new InteractionContainer(
					new SplitSpaceProtocol());
			ArrayList<Point3d> lcLimits = new ArrayList<Point3d>();
			lcLimits.add(localsBounds.get(local)[0]);
			lcLimits.add(localsBounds.get(local)[1]);
			ArrayList<ProtoEntity> entities = entitiesAssigned.get(local);
			// bounds + assigned entities + scale
			// TODO DO WE ADD THE MAX ROTATION/TRANSLATION??
			Object[] array = new Object[] { lcLimits, entities,
					conf.getScale(), new Integer(indexes) };
			indexes += this.cards[listOfLocals.indexOf(local)];
			params[0] = array;
			ic.makeInteraction(SplitSpaceProtocol.INTERACTIONSENDLOCALSDATA,
					params);
			if (!Messages.sendInteraction(this, local, ic))
				return false;
		}
		return true;
	}

	/**
	 * Splits the simulation space between the LocalCoordinators on the basis of
	 * the available space. It simply calculates the offset, i.e. the space
	 * occupied by every coordinator in the direction of cut. The bounds are
	 * then calculated by simply adding the offset, i.e. shifting the bounds on
	 * the specific axis chosen.
	 * 
	 * @see it.unicam.cs.cosy.bioshape.core.components.LocalCoordinator
	 */
	private HashMap<Identifier, Point3d[]> splitSpace() {
		HashMap<Identifier, Point3d[]> localBounds = new HashMap<Identifier, Point3d[]>();
		// BASE CASE ---> one coordinator, NO split
		if (listOfLocals.size() == 1) {
			localBounds.put(listOfLocals.get(0), new Point3d[] {
					new Point3d(bounds[0]), new Point3d(bounds[1]) });
			return localBounds;
		}
		// MANY COORDINATORS ---> switch for coordinate cut
		double offset = 0; // interval offset
		Point3d s = new Point3d(bounds[0]); // start interval
		Point3d e = new Point3d(bounds[1]); // end interval
		switch (SPLITTYPE) {
		case SPLITZ:
			offset = (bounds[1].z - bounds[0].z) / listOfLocals.size();
			conf.setOffset(offset);
			conf.setLocals(listOfLocals.size());
			// FIRST
			e.set(bounds[1].x, bounds[1].y, bounds[0].z + offset);
			localBounds.put(listOfLocals.get(0), new Point3d[] {
					new Point3d(s), new Point3d(e) });
			// MIDDLE
			for (int i = 1; i < listOfLocals.size() - 1; i++) {
				s.z += offset;
				e.z += offset;
				localBounds.put(listOfLocals.get(i), new Point3d[] {
						new Point3d(s), new Point3d(e) });
			}
			// END
			s.z += offset;
			e.set(bounds[1]);
			localBounds.put(listOfLocals.get(listOfLocals.size()),
					new Point3d[] { new Point3d(s), new Point3d(e) });
			break;
		case SPLITY:
			offset = (bounds[1].y - bounds[0].y) / listOfLocals.size();
			conf.setOffset(offset);
			conf.setLocals(listOfLocals.size());
			e.set(bounds[1].x, bounds[0].y, bounds[1].z);
			for (int i = 0; i < listOfLocals.size(); i++) {
				s.y += offset;
				e.y += offset;
				if (i == 0)
					s.set(bounds[0]);
				else if (i == listOfLocals.size() - 1)
					s.set(bounds[1]);
				localBounds.put(listOfLocals.get(i), new Point3d[] {
						new Point3d(s), new Point3d(e) });
			}
			break;
		case SPLITX:
			offset = (bounds[1].x - bounds[0].x) / listOfLocals.size();
			conf.setOffset(offset);
			conf.setLocals(listOfLocals.size());
			for (int i = 0; i < listOfLocals.size(); i++) {
				if (i == 0)
					e.set(bounds[0].x + offset, bounds[1].y, bounds[1].z);
				else if (i == listOfLocals.size() - 1) {
					s.x += offset;
					e.set(bounds[1]);
				} else {
					s.x += offset;
					e.x += offset;
				}
				localBounds.put(listOfLocals.get(i), new Point3d[] {
						new Point3d(s), new Point3d(e) });
			}
		}
		return localBounds;
	}

	/**
	 * Sends the walls to the {@code LocalCoordinator}s. The sending is
	 * dependent on the cut coordinate. Border locals do not receive the
	 * opposite wall whereas all the central locals do not receive the border
	 * walls of the cut.<br>
	 * The indices passed to the {@link #sendingSelectedWalls(int, int)} method
	 * refer to the position of the walls in the corresponding data structure.
	 * In particular walls at positions 0 and 1 are the walls for the Z-axis,
	 * indices 2 and 3 are the walls for the Y-axis and the last two are walls
	 * for the X-axis. Indeed, this is the order in which the walls are
	 * generated and added to the data structure in the method
	 * {@link #generateWalls()}.
	 * 
	 * @see #sendingSelectedWalls(int, int)
	 * @see #generateWalls()
	 */
	private boolean sendWalls() {
		// BASE CASE: only one coordinator
		if (listOfLocals.size() == 1)
			return sendWalls(listOfLocals.get(0), spaceWalls);

		// MORE than ONE coordinator...
		switch (SPLITTYPE) {
		case SPLITX:
			// walls 1 and 3 are not sent to the central locals
			if (!sendingSelectedWalls(4, 5))
				return false;
			break;
		case SPLITY:
			// walls 4 and 5 are not sent to the central locals
			if (!sendingSelectedWalls(3, 2))
				return false;
			break;
		case SPLITZ:
			// walls 0 and 2 are not sent to the central locals
			if (!sendingSelectedWalls(1, 0))
				return false;
		}
		return true;
	}

	/**
	 * Sends the walls to the {@code LocalCoordinator}s. The first coordinator
	 * of the row does not receive the opposite wall on the coordinate of the
	 * cutting and vice versa for the last one. Instead the central locals do
	 * not receive BOTH the wall of the cutting coordinate.
	 * 
	 * @param first
	 *            The first wall corresponding to the cut coordinate
	 * @param second
	 *            The second wall corresponding to the cut coordinate
	 */
	private boolean sendingSelectedWalls(int first, int second) {
		// FIRST LOCAL
		ArrayList<WallEntity> temp = new ArrayList<WallEntity>();
		temp.addAll(spaceWalls);
		temp.remove(second);
		if (!sendWalls(listOfLocals.get(0), temp))
			return false;
		// MIDDLE ONES
		temp.clear();
		temp.addAll(spaceWalls);
		temp.remove(first);
		temp.remove(second);
		for (int i = 1; i < listOfLocals.size() - 1; i++) {
			if (!sendWalls(listOfLocals.get(i), temp))
				return false;
		}
		// LAST LOCAL
		temp.clear();
		temp.addAll(spaceWalls);
		temp.remove(first);
		if (!sendWalls(listOfLocals.get(listOfLocals.size() - 1), temp))
			return false;
		return true;
	}

	/**
	 * @param lc
	 *            {@code Identifier} of the {@code LocalCoordinator} to which
	 *            send the walls
	 * @param walls
	 *            set of {@code WallEntities} to be sent, according to the
	 *            <i>position</i> of the {@code LocalCoordinator}.
	 */
	private boolean sendWalls(Identifier lc, ArrayList<WallEntity> walls) {
		InteractionContainer ic = new InteractionContainer(
				new SplitSpaceProtocol());
		Object[] params = new Object[1];
		params[0] = walls;
		ic.makeInteraction(SplitSpaceProtocol.INTERACTIONSENDWALLS, params);
		if (!Messages.sendInteraction(this, lc, ic)) {
			HermesLogger.log(HermesLogger.INFO, this.getAgentName()
					+ " - something wrong while sending "
					+ "simulation walls to LC#" + listOfLocals.indexOf(lc)
					+ "...");
			return false;
		}
		HermesLogger.log(HermesLogger.INFO, this.getAgentName()
				+ " - simulation walls sent to LC#" + listOfLocals.indexOf(lc)
				+ "...");
		return true;
	}

	/**
	 * Receives requests from the {@code LocalCoordinator}s to check ownership.
	 * Once all the requests are received, they are concurrently managed over a
	 * thread pool.
	 */
	private void handleSpatialRequests() {
		results.clear();
		int nReq = 0;
		while (nReq < listOfLocals.size()) {
			Message<?> msg = Messages
					.receivedInteractionWithError(this, listOfLocals
							.toArray(new Identifier[listOfLocals.size()]),
							PhantomProtocol.INTERACTIONTARGETREQ);
			// typically NULL because the SimulationAgent stopped/ended the
			// simulation
			if (msg == null) {
				respond = false;
				return;
			}
			nReq++;
			InteractionContainer ic = (InteractionContainer) msg.getObject();
			@SuppressWarnings("unchecked")
			ArrayList<Entity> shapedEntities = (ArrayList<Entity>) ic
					.getParameter(PhantomProtocol.INTERACTIONTARGETREQ);
			// add the Future result to the support arraylist data structure
			results.add(handlersPool.submit(new LocalHandler(shapedEntities,
					msg.getSenderAgentId(), getLCBoundings())));
		}
		try {
			Map<Identifier, ArrayList<Entity>> finalResult = collectResults(results);
			// returns informations to the LCs
			sendPhantoms(finalResult);
			// TODO receive answer?? Attualmente no...serve?? Penso di no!
			// receiveLocalsAcks();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();// TODO something
		}
	}

	@SuppressWarnings("unchecked")
	private Pair<Identifier, Bounds>[] getLCBoundings() {
		// TODO can be changed to manage spatial reorganisation at runtime
		if (lcBounds != null)
			return lcBounds;
		lcBounds = new Pair[listOfLocals.size()];
		int i = 0;
		for (Identifier id : localsBounds.keySet()) {
			Point3d p1 = localsBounds.get(id)[0];
			Point3d p2 = localsBounds.get(id)[1];
			lcBounds[i++] = new Pair<Identifier, Bounds>(id,
					new BoundingBox(p1, p2));
		}
		return lcBounds;
	}

	private void sendPhantoms(Map<Identifier, ArrayList<Entity>> finalResult) {
		for (Identifier i : finalResult.keySet()) {
			ArrayList<Entity> content = finalResult.get(i);
			InteractionContainer ic = new InteractionContainer(
					new PhantomProtocol());
			ic.makeInteraction(PhantomProtocol.INTERACTIONTARGETRESPPHANTOM,
					content);
			boolean state = Messages.sendInteraction(this, i, ic);
			if (!state)
				System.out.println("Sending error!!"); // TODO something
		}

	}

	private Map<Identifier, ArrayList<Entity>> collectResults(
			List<Future<Map<Identifier, ArrayList<Entity>>>> results)
			throws InterruptedException, ExecutionException {
		Map<Identifier, ArrayList<Entity>> result = new HashMap<Identifier, ArrayList<Entity>>(
				listOfLocals.size());
		for (Identifier id : listOfLocals) {
			result.put(id, new ArrayList<Entity>());
		}
		for (Future<Map<Identifier, ArrayList<Entity>>> f : results) {
			Map<Identifier, ArrayList<Entity>> temp = f.get();
			for (Identifier id : temp.keySet())
				result.get(id).addAll(temp.get(id));
		}
		return result;
	}

	/**
	 * Computes simulation volume on the basis of the bounds given as input. The
	 * absolute function is used to guarantee that the DIFFERENCE between the
	 * single dimensions is computed and the volume is always positive.
	 */
	private void computeSpaceVolume() {
		this.spaceVolume = (Math.abs(this.bounds[1].getX()
				- this.bounds[0].getX())
				* Math.abs(this.bounds[1].getY() - this.bounds[0].getY()) * Math
				.abs(this.bounds[1].getZ() - this.bounds[0].getZ()));
	}

	/**
	 * Creates the Space walls, i.e. the entities that limit the simulation
	 * space.<br>
	 * The walls are {@link WallEntity} that delimit the space in order to
	 * retain the {@link Entity} object into the simulation volume.<br>
	 * Each wall dimension is computed referring to the respective Space
	 * dimension. A wall has at least the size of the corresponding space
	 * dimension and volume to ensure that the tunneling phenomenon could not
	 * occur.<br>
	 * For definition, tunneling occurs if a particle tunnels through a barrier
	 * that it could not surmount because the particle total kinetic energy is
	 * lower than the potential energy of the barrier. Hence, the longer are
	 * distances covered by entities in a movement, the bigger must be walls to
	 * prevent tunneling.
	 * 
	 * @return an {@code ArrayList} of {@code WallEntities}
	 */
	private ArrayList<WallEntity> generateWalls() {
		Point3d min = new Point3d();
		Point3d max = new Point3d();
		double diffX = bounds[1].x - bounds[0].x;
		double diffY = bounds[1].y - bounds[0].y;
		double diffZ = bounds[1].z - bounds[0].z;
		// walls
		ArrayList<WallEntity> walls = new ArrayList<WallEntity>(6);
		Point3d simulationCenter = new Point3d((bounds[1].x + bounds[0].x) / 2,
				(bounds[1].y + bounds[0].y) / 2,
				(bounds[1].z + bounds[0].z) / 2);
		// ----------wall 2(behind)-----------
		min.x = bounds[0].x - diffX;
		min.y = bounds[0].y - diffY;
		min.z = bounds[1].z;
		max.x = bounds[1].x + diffX;
		max.y = bounds[1].y + diffY;
		max.z = bounds[1].z + diffZ;
		WallEntity wall = new WallEntity(2, min, max);
		wall.translate(simulationCenter);
		wall.translate(new Point3d(0, 0, diffZ));
		walls.add(wall);
		// ----------wall 0(front)(same dimensions as "behind" one)-----------
		wall = new WallEntity(0, min, max);
		wall.translate(simulationCenter);
		wall.translate(new Point3d(0, 0, -diffZ));
		walls.add(wall);
		//
		// ----------muro 4(upper)-----------
		min.x = bounds[0].x - diffX;
		min.y = bounds[1].y;
		min.z = bounds[0].z - diffZ;
		max.x = bounds[1].x + diffX;
		max.y = bounds[1].y + diffY;
		max.z = bounds[1].z + diffZ;
		wall = new WallEntity(4, min, max);
		wall.translate(simulationCenter);
		wall.translate(new Point3d(0, diffY, 0));
		walls.add(wall);
		// ----------muro 5(lower)(same dimensions as "upper" one)-----------
		wall = new WallEntity(5, min, max);
		wall.translate(simulationCenter);
		wall.translate(new Point3d(0, -diffY, 0));
		walls.add(wall);
		//
		// ----------muro 3(left)-----------
		min.x = bounds[0].x - diffX;
		min.y = bounds[0].y - diffY;
		min.z = bounds[0].z - diffZ;
		max.x = bounds[0].x;
		max.y = bounds[1].y + diffY;
		max.z = bounds[1].z + diffZ;
		wall = new WallEntity(3, min, max);
		wall.translate(simulationCenter);
		wall.translate(new Point3d(-diffX, 0, 0));
		walls.add(wall);
		// ----------muro 1(right)(same dimensions as "left" one)-----------
		wall = new WallEntity(1, min, max);
		wall.translate(simulationCenter);
		wall.translate(new Point3d(diffX, 0, 0));
		walls.add(wall);
		//
		return walls;
	}

	private boolean receiveLocalsAcks() {
		int nAck = 0;
		Identifier[] ids = new Identifier[listOfLocals.size()];
		listOfLocals.toArray(ids);
		while (nAck < listOfLocals.size()) {
			// TODO with error
			if (Messages.receiveAck(this, ids) == null) {
				HermesLogger.log(HermesLogger.INFO, this.getAgentName()
						+ " - Something wrong while receiving "
						+ "LocalCoordinators messages. Simulation aborted.");
				return false;
			}
			nAck++;
		}
		return true;
	}
}